// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/public/test/layouttest_support.h"

#include <stddef.h>
#include <utility>

#include "base/callback.h"
#include "base/lazy_instance.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "cc/output/copy_output_request.h"
#include "cc/test/pixel_test_output_surface.h"
#include "cc/test/test_compositor_frame_sink.h"
#include "components/test_runner/test_common.h"
#include "components/test_runner/web_frame_test_proxy.h"
#include "components/test_runner/web_view_test_proxy.h"
#include "components/test_runner/web_widget_test_proxy.h"
#include "content/browser/bluetooth/bluetooth_device_chooser_controller.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/common/renderer.mojom.h"
#include "content/common/site_isolation_policy.h"
#include "content/public/common/page_state.h"
#include "content/public/common/screen_info.h"
#include "content/public/renderer/renderer_gamepad_provider.h"
#include "content/renderer/fetchers/manifest_fetcher.h"
#include "content/renderer/gpu/render_widget_compositor.h"
#include "content/renderer/history_entry.h"
#include "content/renderer/history_serialization.h"
#include "content/renderer/input/render_widget_input_handler_delegate.h"
#include "content/renderer/layout_test_dependencies.h"
#include "content/renderer/render_frame_impl.h"
#include "content/renderer/render_thread_impl.h"
#include "content/renderer/render_view_impl.h"
#include "content/renderer/render_widget.h"
#include "content/renderer/renderer_blink_platform_impl.h"
#include "content/shell/common/shell_switches.h"
#include "gpu/ipc/service/image_transport_surface.h"
#include "services/ui/public/cpp/gpu/context_provider_command_buffer.h"
#include "third_party/WebKit/public/platform/WebFloatRect.h"
#include "third_party/WebKit/public/platform/WebGamepads.h"
#include "third_party/WebKit/public/platform/WebInputEvent.h"
#include "third_party/WebKit/public/platform/WebRect.h"
#include "third_party/WebKit/public/platform/modules/device_orientation/WebDeviceMotionData.h"
#include "third_party/WebKit/public/platform/modules/device_orientation/WebDeviceOrientationData.h"
#include "third_party/WebKit/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/WebKit/public/web/WebHistoryItem.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "ui/events/blink/blink_event_util.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/icc_profile.h"
#include "ui/gfx/test/icc_profiles.h"

#if defined(OS_MACOSX)
#include "content/browser/frame_host/popup_menu_helper_mac.h"
#elif defined(OS_WIN)
#include "content/child/font_warmup_win.h"
#include "third_party/WebKit/public/web/win/WebFontRendering.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/ports/SkFontMgr.h"
#include "third_party/skia/include/ports/SkTypeface_win.h"
#include "ui/gfx/win/direct_write.h"
#endif

using blink::WebDeviceMotionData;
using blink::WebDeviceOrientationData;
using blink::WebGamepad;
using blink::WebGamepads;
using blink::WebRect;
using blink::WebSize;

namespace content {

namespace {

    base::LazyInstance<ViewProxyCreationCallback>::Leaky
        g_view_test_proxy_callback
        = LAZY_INSTANCE_INITIALIZER;

    base::LazyInstance<WidgetProxyCreationCallback>::Leaky
        g_widget_test_proxy_callback
        = LAZY_INSTANCE_INITIALIZER;

    base::LazyInstance<FrameProxyCreationCallback>::Leaky
        g_frame_test_proxy_callback
        = LAZY_INSTANCE_INITIALIZER;

    using WebViewTestProxyType = test_runner::WebViewTestProxy<RenderViewImpl,
        CompositorDependencies*,
        const mojom::CreateViewParams&>;
    using WebWidgetTestProxyType = test_runner::WebWidgetTestProxy<RenderWidget,
        int32_t,
        CompositorDependencies*,
        blink::WebPopupType,
        const ScreenInfo&,
        bool,
        bool,
        bool>;
    using WebFrameTestProxyType = test_runner::WebFrameTestProxy<RenderFrameImpl,
        const RenderFrameImpl::CreateParams&>;

    RenderViewImpl* CreateWebViewTestProxy(CompositorDependencies* compositor_deps,
        const mojom::CreateViewParams& params)
    {
        WebViewTestProxyType* render_view_proxy = new WebViewTestProxyType(compositor_deps, params);
        if (g_view_test_proxy_callback == 0)
            return render_view_proxy;
        g_view_test_proxy_callback.Get().Run(render_view_proxy, render_view_proxy);
        return render_view_proxy;
    }

    RenderWidget* CreateWebWidgetTestProxy(int32_t routing_id,
        CompositorDependencies* compositor_deps,
        blink::WebPopupType popup_type,
        const ScreenInfo& screen_info,
        bool swapped_out,
        bool hidden,
        bool never_visible)
    {
        WebWidgetTestProxyType* render_widget_proxy = new WebWidgetTestProxyType(
            routing_id, compositor_deps, popup_type, screen_info, swapped_out, hidden,
            never_visible);
        return render_widget_proxy;
    }

    void RenderWidgetInitialized(RenderWidget* render_widget)
    {
        WebWidgetTestProxyType* render_widget_proxy = static_cast<WebWidgetTestProxyType*>(render_widget);
        if (!g_widget_test_proxy_callback.Get().is_null()) {
            g_widget_test_proxy_callback.Get().Run(render_widget->GetWebWidget(),
                render_widget_proxy);
        }
    }

    RenderFrameImpl* CreateWebFrameTestProxy(
        const RenderFrameImpl::CreateParams& params)
    {
        WebFrameTestProxyType* render_frame_proxy = new WebFrameTestProxyType(params);
        if (g_frame_test_proxy_callback == 0)
            return render_frame_proxy;
        g_frame_test_proxy_callback.Get().Run(render_frame_proxy, render_frame_proxy);
        return render_frame_proxy;
    }

    float GetWindowToViewportScale(RenderWidget* render_widget)
    {
        blink::WebFloatRect rect(0, 0, 1.0f, 0.0);
        render_widget->convertWindowToViewport(&rect);
        return rect.width;
    }

#if defined(OS_WIN)
    // DirectWrite only has access to %WINDIR%\Fonts by default. For developer
    // side-loading, support kRegisterFontFiles to allow access to additional fonts.
    void RegisterSideloadedTypefaces(SkFontMgr* fontmgr)
    {
        std::vector<std::string> files = switches::GetSideloadFontFiles();
        for (std::vector<std::string>::const_iterator i(files.begin());
             i != files.end();
             ++i) {
            SkTypeface* typeface = fontmgr->createFromFile(i->c_str());
            blink::WebFontRendering::addSideloadedFontForTesting(typeface);
        }
    }
#endif // OS_WIN

} // namespace

test_runner::WebViewTestProxyBase* GetWebViewTestProxyBase(
    RenderView* render_view)
{
    WebViewTestProxyType* render_view_proxy = static_cast<WebViewTestProxyType*>(render_view);
    return static_cast<test_runner::WebViewTestProxyBase*>(render_view_proxy);
}

test_runner::WebFrameTestProxyBase* GetWebFrameTestProxyBase(
    RenderFrame* render_frame)
{
    WebFrameTestProxyType* render_frame_proxy = static_cast<WebFrameTestProxyType*>(render_frame);
    return static_cast<test_runner::WebFrameTestProxyBase*>(render_frame_proxy);
}

test_runner::WebWidgetTestProxyBase* GetWebWidgetTestProxyBase(
    blink::WebLocalFrame* frame)
{
    DCHECK(frame);
    RenderFrame* local_root = RenderFrame::FromWebFrame(frame->localRoot());
    DCHECK(local_root);
    // TODO(lfg): Simplify once RenderView no longer inherits from RenderWidget.
    if (local_root->IsMainFrame()) {
        test_runner::WebViewTestProxyBase* web_view_test_proxy_base = GetWebViewTestProxyBase(local_root->GetRenderView());
        auto* web_widget_test_proxy_base = static_cast<test_runner::WebWidgetTestProxyBase*>(
            web_view_test_proxy_base);
        DCHECK(web_widget_test_proxy_base->web_widget()->isWebView());
        return web_widget_test_proxy_base;
    } else {
        RenderWidget* render_widget = static_cast<RenderFrameImpl*>(local_root)->GetRenderWidget();
        DCHECK(render_widget);
        WebWidgetTestProxyType* render_widget_proxy = static_cast<WebWidgetTestProxyType*>(render_widget);
        auto* web_widget_test_proxy_base = static_cast<test_runner::WebWidgetTestProxyBase*>(render_widget_proxy);
        DCHECK(web_widget_test_proxy_base->web_widget()->isWebFrameWidget());
        return web_widget_test_proxy_base;
    }
}

RenderWidget* GetRenderWidget(
    test_runner::WebWidgetTestProxyBase* web_widget_test_proxy_base)
{
    DCHECK(web_widget_test_proxy_base);

    blink::WebWidget* widget = web_widget_test_proxy_base->web_widget();
    // TODO(lfg): Simplify once RenderView no longer inherits from RenderWidget.
    if (widget->isWebView()) {
        WebViewTestProxyType* render_view_proxy = static_cast<WebViewTestProxyType*>(web_widget_test_proxy_base);
        RenderViewImpl* render_view_impl = static_cast<RenderViewImpl*>(render_view_proxy);
        return render_view_impl;
    } else if (widget->isWebFrameWidget()) {
        WebWidgetTestProxyType* render_widget_proxy = static_cast<WebWidgetTestProxyType*>(web_widget_test_proxy_base);
        return static_cast<RenderWidget*>(render_widget_proxy);
    } else {
        NOTREACHED();
        return nullptr;
    }
}

void EnableWebTestProxyCreation(
    const ViewProxyCreationCallback& view_proxy_creation_callback,
    const WidgetProxyCreationCallback& widget_proxy_creation_callback,
    const FrameProxyCreationCallback& frame_proxy_creation_callback)
{
    g_view_test_proxy_callback.Get() = view_proxy_creation_callback;
    g_widget_test_proxy_callback.Get() = widget_proxy_creation_callback;
    g_frame_test_proxy_callback.Get() = frame_proxy_creation_callback;
    RenderViewImpl::InstallCreateHook(CreateWebViewTestProxy);
    RenderWidget::InstallCreateHook(CreateWebWidgetTestProxy,
        RenderWidgetInitialized);
    RenderFrameImpl::InstallCreateHook(CreateWebFrameTestProxy);
}

void FetchManifestDoneCallback(std::unique_ptr<ManifestFetcher> fetcher,
    const FetchManifestCallback& callback,
    const blink::WebURLResponse& response,
    const std::string& data)
{
    // |fetcher| will be autodeleted here as it is going out of scope.
    callback.Run(response, data);
}

void FetchManifest(blink::WebView* view, const GURL& url,
    const FetchManifestCallback& callback)
{
    ManifestFetcher* fetcher = new ManifestFetcher(url);
    std::unique_ptr<ManifestFetcher> autodeleter(fetcher);

    // Start is called on fetcher which is also bound to the callback.
    // A raw pointer is used instead of a scoped_ptr as base::Passes passes
    // ownership and thus nulls the scoped_ptr. On MSVS this happens before
    // the call to Start, resulting in a crash.
    fetcher->Start(view->mainFrame(),
        false,
        base::Bind(&FetchManifestDoneCallback,
            base::Passed(&autodeleter),
            callback));
}

void SetMockGamepadProvider(std::unique_ptr<RendererGamepadProvider> provider)
{
    RenderThreadImpl::current()
        ->blink_platform_impl()
        ->SetPlatformEventObserverForTesting(blink::WebPlatformEventTypeGamepad,
            std::move(provider));
}

void SetMockDeviceLightData(const double data)
{
    RendererBlinkPlatformImpl::SetMockDeviceLightDataForTesting(data);
}

void SetMockDeviceMotionData(const WebDeviceMotionData& data)
{
    RendererBlinkPlatformImpl::SetMockDeviceMotionDataForTesting(data);
}

void SetMockDeviceOrientationData(const WebDeviceOrientationData& data)
{
    RendererBlinkPlatformImpl::SetMockDeviceOrientationDataForTesting(data);
}

namespace {

    // Invokes a callback on commit (on the main thread) to obtain the output
    // surface that should be used, then asks that output surface to submit the copy
    // request at SwapBuffers time.
    class CopyRequestSwapPromise : public cc::SwapPromise {
    public:
        using FindCompositorFrameSinkCallback = base::Callback<cc::TestCompositorFrameSink*()>;
        CopyRequestSwapPromise(
            std::unique_ptr<cc::CopyOutputRequest> request,
            FindCompositorFrameSinkCallback find_compositor_frame_sink_callback)
            : copy_request_(std::move(request))
            , find_compositor_frame_sink_callback_(
                  std::move(find_compositor_frame_sink_callback))
        {
        }

        // cc::SwapPromise implementation.
        void OnCommit() override
        {
            compositor_frame_sink_from_commit_ = find_compositor_frame_sink_callback_.Run();
            DCHECK(compositor_frame_sink_from_commit_);
        }
        void DidActivate() override { }
        void WillSwap(cc::CompositorFrameMetadata*) override
        {
            compositor_frame_sink_from_commit_->RequestCopyOfOutput(
                std::move(copy_request_));
        }
        void DidSwap() override { }
        DidNotSwapAction DidNotSwap(DidNotSwapReason r) override
        {
            // The compositor should always swap in layout test mode.
            NOTREACHED() << "did not swap for reason " << r;
            return DidNotSwapAction::BREAK_PROMISE;
        }
        int64_t TraceId() const override { return 0; }

    private:
        std::unique_ptr<cc::CopyOutputRequest> copy_request_;
        FindCompositorFrameSinkCallback find_compositor_frame_sink_callback_;
        cc::TestCompositorFrameSink* compositor_frame_sink_from_commit_ = nullptr;
    };

} // namespace

class LayoutTestDependenciesImpl : public LayoutTestDependencies,
                                   public cc::TestCompositorFrameSinkClient {
public:
    std::unique_ptr<cc::CompositorFrameSink> CreateCompositorFrameSink(
        int32_t routing_id,
        scoped_refptr<gpu::GpuChannelHost> gpu_channel,
        scoped_refptr<cc::ContextProvider> compositor_context_provider,
        scoped_refptr<cc::ContextProvider> worker_context_provider,
        gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
        CompositorDependencies* deps) override
    {
        // This could override the GpuChannel for a CompositorFrameSink that was
        // previously being created but in that case the old GpuChannel would be
        // lost as would the CompositorFrameSink.
        gpu_channel_ = gpu_channel;

        auto* task_runner = deps->GetCompositorImplThreadTaskRunner().get();
        bool synchronous_composite = !task_runner;
        if (!task_runner)
            task_runner = base::ThreadTaskRunnerHandle::Get().get();

        ScreenInfo dummy_screen_info;
        cc::LayerTreeSettings settings = RenderWidgetCompositor::GenerateLayerTreeSettings(
            *base::CommandLine::ForCurrentProcess(), deps, 1.f,
            dummy_screen_info);

        auto compositor_frame_sink = base::MakeUnique<cc::TestCompositorFrameSink>(
            std::move(compositor_context_provider),
            std::move(worker_context_provider), nullptr /* shared_bitmap_manager */,
            gpu_memory_buffer_manager, settings.renderer_settings, task_runner,
            synchronous_composite, false /* force_disable_reclaim_resources */);
        compositor_frame_sink->SetClient(this);
        compositor_frame_sinks_[routing_id] = compositor_frame_sink.get();
        return std::move(compositor_frame_sink);
    }

    std::unique_ptr<cc::SwapPromise> RequestCopyOfOutput(
        int32_t routing_id,
        std::unique_ptr<cc::CopyOutputRequest> request) override
    {
        // Note that we can't immediately check compositor_frame_sinks_, since it
        // may not have been created yet. Instead, we wait until OnCommit to find
        // the currently active CompositorFrameSink for the given RenderWidget
        // routing_id.
        return base::MakeUnique<CopyRequestSwapPromise>(
            std::move(request),
            base::Bind(
                &LayoutTestDependenciesImpl::FindCompositorFrameSink,
                // |this| will still be valid, because its lifetime is tied to
                // RenderThreadImpl, which outlives layout test execution.
                base::Unretained(this), routing_id));
    }

    // TestCompositorFrameSinkClient implementation.
    std::unique_ptr<cc::OutputSurface> CreateDisplayOutputSurface(
        scoped_refptr<cc::ContextProvider> compositor_context_provider) override
    {
        // This is for an offscreen context for the compositor. So the default
        // framebuffer doesn't need alpha, depth, stencil, antialiasing.
        gpu::gles2::ContextCreationAttribHelper attributes;
        attributes.alpha_size = -1;
        attributes.depth_size = 0;
        attributes.stencil_size = 0;
        attributes.samples = 0;
        attributes.sample_buffers = 0;
        attributes.bind_generates_resource = false;
        attributes.lose_context_when_out_of_memory = true;
        const bool automatic_flushes = false;
        const bool support_locking = false;

        auto context_provider = make_scoped_refptr(new ui::ContextProviderCommandBuffer(
            gpu_channel_, gpu::GPU_STREAM_DEFAULT,
            gpu::GpuStreamPriority::NORMAL, gpu::kNullSurfaceHandle,
            GURL("chrome://gpu/"
                 "LayoutTestDependenciesImpl::CreateOutputSurface"),
            automatic_flushes, support_locking, gpu::SharedMemoryLimits(),
            attributes, nullptr,
            ui::command_buffer_metrics::OFFSCREEN_CONTEXT_FOR_TESTING));
        context_provider->BindToCurrentThread();

        bool flipped_output_surface = false;
        return base::MakeUnique<cc::PixelTestOutputSurface>(
            std::move(context_provider), flipped_output_surface);
    }
    void DisplayReceivedCompositorFrame(
        const cc::CompositorFrame& frame) override { }
    void DisplayWillDrawAndSwap(
        bool will_draw_and_swap,
        const cc::RenderPassList& render_passes) override { }
    void DisplayDidDrawAndSwap() override { }

private:
    cc::TestCompositorFrameSink* FindCompositorFrameSink(int32_t routing_id)
    {
        auto it = compositor_frame_sinks_.find(routing_id);
        return it == compositor_frame_sinks_.end() ? nullptr : it->second;
    }

    // Entries are not removed, so this map can grow. However, it is only used in
    // layout tests, so this memory usage does not occur in production.
    // Entries in this map will outlive the output surface, because this object is
    // owned by RenderThreadImpl, which outlives layout test execution.
    std::unordered_map<int32_t, cc::TestCompositorFrameSink*>
        compositor_frame_sinks_;
    scoped_refptr<gpu::GpuChannelHost> gpu_channel_;
};

void EnableRendererLayoutTestMode()
{
    RenderThreadImpl::current()->set_layout_test_dependencies(
        base::MakeUnique<LayoutTestDependenciesImpl>());

#if defined(OS_WIN)
    RegisterSideloadedTypefaces(SkFontMgr_New_DirectWrite().get());
#endif
}

void EnableBrowserLayoutTestMode()
{
#if defined(OS_MACOSX)
    gpu::ImageTransportSurface::SetAllowOSMesaForTesting(true);
    PopupMenuHelper::DontShowPopupMenuForTesting();
#endif
    RenderWidgetHostImpl::DisableResizeAckCheckForTesting();
}

int GetLocalSessionHistoryLength(RenderView* render_view)
{
    return static_cast<RenderViewImpl*>(render_view)->GetLocalSessionHistoryLengthForTesting();
}

void SyncNavigationState(RenderView* render_view)
{
    // TODO(creis): Add support for testing in OOPIF-enabled modes.
    // See https://crbug.com/477150.
    if (SiteIsolationPolicy::UseSubframeNavigationEntries())
        return;
    static_cast<RenderViewImpl*>(render_view)->SendUpdateState();
}

void SetFocusAndActivate(RenderView* render_view, bool enable)
{
    static_cast<RenderViewImpl*>(render_view)->SetFocusAndActivateForTesting(enable);
}

void ForceResizeRenderView(RenderView* render_view,
    const WebSize& new_size)
{
    RenderViewImpl* render_view_impl = static_cast<RenderViewImpl*>(render_view);
    render_view_impl->ForceResizeForTesting(new_size);
}

void SetDeviceScaleFactor(RenderView* render_view, float factor)
{
    static_cast<RenderViewImpl*>(render_view)->SetDeviceScaleFactorForTesting(factor);
}

float GetWindowToViewportScale(RenderView* render_view)
{
    return GetWindowToViewportScale(
        static_cast<RenderViewImpl*>(render_view)->GetWidget());
}

std::unique_ptr<blink::WebInputEvent> TransformScreenToWidgetCoordinates(
    test_runner::WebWidgetTestProxyBase* web_widget_test_proxy_base,
    const blink::WebInputEvent& event)
{
    DCHECK(web_widget_test_proxy_base);
    RenderWidget* render_widget = GetRenderWidget(web_widget_test_proxy_base);

    blink::WebRect view_rect = render_widget->viewRect();
    float scale = GetWindowToViewportScale(render_widget);
    gfx::Vector2d delta(-view_rect.x, -view_rect.y);
    return ui::TranslateAndScaleWebInputEvent(event, delta, scale);
}

gfx::ICCProfile GetTestingICCProfile(const std::string& name)
{
    if (name == "genericRGB") {
        return gfx::ICCProfileForTestingGenericRGB();
    } else if (name == "sRGB") {
        return gfx::ICCProfileForTestingSRGB();
    } else if (name == "test" || name == "colorSpin") {
        return gfx::ICCProfileForTestingColorSpin();
    } else if (name == "adobeRGB") {
        return gfx::ICCProfileForTestingAdobeRGB();
    }
    return gfx::ICCProfile();
}

void SetDeviceColorProfile(
    RenderView* render_view, const gfx::ICCProfile& icc_profile)
{
    static_cast<RenderViewImpl*>(render_view)
        ->GetWidget()
        ->SetDeviceColorProfileForTesting(icc_profile.GetData());
}

void SetTestBluetoothScanDuration()
{
    BluetoothDeviceChooserController::SetTestScanDurationForTesting();
}

void UseSynchronousResizeMode(RenderView* render_view, bool enable)
{
    static_cast<RenderViewImpl*>(render_view)->UseSynchronousResizeModeForTesting(enable);
}

void EnableAutoResizeMode(RenderView* render_view,
    const WebSize& min_size,
    const WebSize& max_size)
{
    static_cast<RenderViewImpl*>(render_view)->EnableAutoResizeForTesting(min_size, max_size);
}

void DisableAutoResizeMode(RenderView* render_view, const WebSize& new_size)
{
    static_cast<RenderViewImpl*>(render_view)->DisableAutoResizeForTesting(new_size);
}

// Returns True if node1 < node2.
bool HistoryEntryCompareLess(HistoryEntry::HistoryNode* node1,
    HistoryEntry::HistoryNode* node2)
{
    base::string16 target1 = node1->item().target();
    base::string16 target2 = node2->item().target();
    return base::CompareCaseInsensitiveASCII(target1, target2) < 0;
}

std::string DumpHistoryItem(HistoryEntry::HistoryNode* node,
    int indent,
    bool is_current_index)
{
    std::string result;

    const blink::WebHistoryItem& item = node->item();
    if (is_current_index) {
        result.append("curr->");
        result.append(indent - 6, ' '); // 6 == "curr->".length()
    } else {
        result.append(indent, ' ');
    }

    std::string url = test_runner::NormalizeLayoutTestURL(item.urlString().utf8());
    result.append(url);
    if (!item.target().isEmpty()) {
        result.append(" (in frame \"");
        result.append(item.target().utf8());
        result.append("\")");
    }
    result.append("\n");

    std::vector<HistoryEntry::HistoryNode*> children = node->children();
    if (!children.empty()) {
        std::sort(children.begin(), children.end(), HistoryEntryCompareLess);
        for (size_t i = 0; i < children.size(); ++i)
            result += DumpHistoryItem(children[i], indent + 4, false);
    }

    return result;
}

std::string DumpBackForwardList(std::vector<PageState>& page_state,
    size_t current_index)
{
    std::string result;
    result.append("\n============== Back Forward List ==============\n");
    for (size_t index = 0; index < page_state.size(); ++index) {
        std::unique_ptr<HistoryEntry> entry(
            PageStateToHistoryEntry(page_state[index]));
        result.append(
            DumpHistoryItem(entry->root_history_node(),
                8,
                index == current_index));
    }
    result.append("===============================================\n");
    return result;
}

void SchedulerRunIdleTasks(const base::Closure& callback)
{
    blink::scheduler::RendererScheduler* scheduler = content::RenderThreadImpl::current()->GetRendererScheduler();
    blink::scheduler::RunIdleTasksForTesting(scheduler, callback);
}

void ForceTextInputStateUpdateForRenderFrame(RenderFrame* frame)
{
    if (auto* render_widget = static_cast<RenderFrameImpl*>(frame)->GetRenderWidget()) {
        render_widget->ShowVirtualKeyboard();
    }
}

} // namespace content
